package lua

import "fmt"

//lua5.3 code-generator
const (
	MAXUPVAL = 255
	LUA_ENV  = "_ENV"
)

//expression descriptor
const (
	VVOID = iota
	VNIL
	VTRUE
	VFALSE
	VK
	VKFLT
	VKINT
	VLOCAL
	VUPVAL
	VINDEXED
	VJMP
	VCALL
	VVARARG
)

type funcState struct {
	f       *Proto
	prev    *funcState
	bl      *blockCnt //chain of current blocks
	nactvar uint8     //number of active local vars
	freereg uint8     //first free register
}

type blockCnt struct {
	prev    *blockCnt //
	nactvar uint8     //active vars outside the block
	upval   bool      //true if some variable in the block is an upvalue
	loop    bool      //true if block is a loop
}

type Compiler struct {
	fs *funcState //current function
}

func (c *Compiler) Compile(ast *StatList) *Proto {
	c.open_func()
	c.fs.f.is_vararg = 2               //main function is always declared vararg
	newupvalue(c.fs, LUA_ENV, true, 0) //set environment upvalue

	c.visitStatList(ast)
	fs := c.close_func()
	return fs.f
}

func checklimit(fs *funcState, v, l int, what string) {
	if v > l {
		var where string
		line := fs.f.linedefined
		if line == 0 {
			where = "main function"
		} else {
			where = fmt.Sprintf("function at line %d", line)
		}
		msg := fmt.Sprintf("[codegen-error]:too many %s(limit is %d) in %s", what, l, where)
		panic(msg)
	}
}

func (c *Compiler) open_func() {
	fs := &funcState{f: &Proto{}}

	fs.prev = c.fs
	c.fs = fs
	fs.f.maxstacksize = 2 //registers 0/1 always valid
	c.enter_block(false)
}

func (c *Compiler) close_func() *funcState {
	c.leave_block()

	fs := c.fs
	c.fs = c.fs.prev
	return fs
}

func (c *Compiler) enter_block(loop bool) {
	bl := &blockCnt{loop: loop, nactvar: c.fs.nactvar}
	bl.prev = c.fs.bl
	c.fs.bl = bl
}

func (c *Compiler) leave_block() {
	bl := c.fs.bl
	//TODO some clearing work
	c.fs.bl = bl.prev
}

func newupvalue(fs *funcState, name TString, instack bool, idx int) int {
	checklimit(fs, len(fs.f.upvalues)+1, MAXUPVAL, "upvalues")
	upval := upvalDesc{instack: instack, name: name, idx: uint8(idx)}
	fs.f.upvalues = append(fs.f.upvalues, upval)
	return len(fs.f.upvalues)
}

func (c *Compiler) visitStatList(ast *StatList) {
	for _, s := range ast.stats {
		switch s.(type) {
		case *IfStat:
			c.visitIfStat(s.(*IfStat))
		case *WhileStat:
			c.visitWhileStat(s.(*WhileStat))
		case *DoStat:
			c.visitDoStat(s.(*DoStat))
		case *Fornum:
			c.visitFornum(s.(*Fornum))
		case *Forlist:
			c.visitForlist(s.(*Forlist))
		case *RepeatStat:
			c.visitRepeatStat(s.(*RepeatStat))
		case *FunctionStat:
			c.visitFuncStat(s.(*FunctionStat))
		case *LocalStat:
			c.visitLocalStat(s.(*LocalStat))
		case *LabelStat:
			c.visitLabelStat(s.(*LabelStat))
		case *ReturnStat:
			c.visitReturnStat(s.(*ReturnStat))
		case *GotoStat:
			c.visitGotoStat(s.(*GotoStat))
		case *AssignStat:
			c.visitAssignStat(s.(*AssignStat))
		}
	}
}

func (c *Compiler) visitIfStat(s *IfStat) {

}

func (c *Compiler) visitWhileStat(s *WhileStat) {

}

func (c *Compiler) visitDoStat(s *DoStat) {

}

func (c *Compiler) visitFornum(s *Fornum) {

}

func (c *Compiler) visitForlist(s *Forlist) {

}

func (c *Compiler) visitRepeatStat(s *RepeatStat) {

}

func (c *Compiler) visitFuncStat(s *FunctionStat) {

}

func (c *Compiler) visitLocalStat(s *LocalStat) {

}

func (c *Compiler) visitLabelStat(s *LabelStat) {

}

func (c *Compiler) visitReturnStat(s *ReturnStat) {

}

func (c *Compiler) visitGotoStat(s *GotoStat) {

}

func (c *Compiler) visitAssignStat(s *AssignStat) {

}
